# 客户端

客户端以用户态可执行程序的形式可以运行在容器中，并且通过FUSE将挂载的卷及文件系统接口提供给其它用户态应用。

## 客户端缓存

客户端进程在以下几种情况下会使用客户端缓存。

- 客户端为了减少与资源管理节点的通信负担，会在挂载启动时获取该挂载卷中所有元数据和数据节点的地址，并且进行缓存，后续会定期从资源管理节点进行更新。

- 客户端为了减少与元数据节点的通信，会缓存inode，dentry以及extent元数据信息。通常意义上，读请求应该能够读到之前所有的写入，但是客户端元数据缓存可能会导致多客户端写同一个文件时的一致性问题。所以，CubeFS的设计中，不同客户端，或者说挂载点，可以同时读一个文件，但是不能够同时写一个文件。打开文件时，客户端会强制从元数据节点更新文件元数据信息。

::: warning 注意
不同进程可以从同一个挂载点并发写一个文件，但是需要解决数据并发写导致的错乱问题
:::

由于故障恢复时，raft复制组的主节点有可能发生变化，导致客户端缓存的主节点地址无效。因此，客户端在发送请求收到not
leader回复时，会轮询重试该复制组的所有节点。重试成功后识别出新的主节点，客户端会缓存新的主节点地址。

## 对接FUSE接口

CubeFS客户端通过对接FUSE为提供用户态文件系统接口。

::: tip 提示
之前，性能较低被认为是用户态文件系统最大的缺点。但是经过多年的发展，FUSE已经在性能上有了很大提高。后续，CubeFS会着手开发内核态文件系统客户端。
:::

目前来看，FUSE的writeback cache特性并未达到预期的性能提升。

FUSE默认的写流程走的是directIO接口，使得每次写入长度较小时会有性能问题，因为每次的写请求都会被推送至后端存储。

FUSE的解决方案是writeback cache，即小写入写到缓存页即返回，由内核根据回刷策略推送至后端存储。这样，顺序的小请求会被聚合。

但是，在实际生产中，我们发现writeback cache特性作用非常有限，原因是走writecache的写操作触发了内核balance dirty page的流程，使得本应该是响应时间非常短的写操作仍然会等较长时间才返回，这个问题在小的写入时尤其明显。


## 在线升级或热重启
支持不停止旧客户端进程进行在线升级。新客户端会先恢复旧客户端FUSE读写请求，接替并继续服务旧客户端的数据读写请求。

程序执行如下操作：

1. 旧客户端停止从/dev/fuse读取请求
2. 旧客户端保存上下文信息到本地
   
    上下文信息包括还在fuse客户端和内核之间的、使用中（已经打开open或者没有被驱逐evicted）的inodes/files
3. 旧客户端传输/dev/fuse的文件描述符
   
    使用Unix域套接字（Unix Domain Socket）向新客户端传输文件描述符
    
4. 旧客户端退出，新客户端启动
5. 新客户端恢复上下文
   
    新客户端尝试恢复旧客户端的上下文而不是真实挂载fuse。
6. 恢复/dev/fuse文件描述符
   
    新客户端读取的旧客户端未处理的请求，继续服务。

![LiveUpgrade](./pic/client-live-upgrade.png)

## 客户端预热

客户端为了提高纠删卷的读取效率，可以通过预热功能将纠删码子系统的数据缓存到副本子系统中。副本子系统中的缓存内容会在预热TTL过期后，自动删除。

## 一级缓存（数据缓存）

L1Cache是独立于客户端的本地数据缓存服务，对外提供Put/Get/Delete接口，基于数据块（Block）进行缓存读写、淘汰操作，整体结构如下图所示。

![block cache](./pic/block-cache.png)

L1Cache缓存服务为本机所有打开一级缓存配置的客户端提供缓存服务。

本地缓存数据块与远端存储数据块一一对应，并按块进行索引（BlockKey）访问，数据块索引BlockKey生成方式为：`VolumeName_Inode_hex(FileOffset)`。

::: tip 提示
数据块索引经过两次取模计算将内存数据块结构映射到本地缓存文件，`LocalPath / hash(BlockKey)%512 / hash(BlockKey)%256 / BlockKey`。
:::

L1CacheStoreService统一维护全局的BlockKeys，定期按照LRU进行淘汰。

L1Cache服务重启时自动扫描磁盘上的缓存数据，并重建缓存索引信息。缓存目录的增加和退出不涉及数据迁移，丢失的缓存数据重新缓存，残留的缓存数据最终会被LRU淘汰。